package com.gitee.zw.irmp.common.auth.oauth2;

import com.gitee.zw.irmp.common.auth.sso.SSOProcessor;
import com.gitee.zw.irmp.common.exception.AuthenticationException;
import com.gitee.zw.irmp.common.auth.JWTFilter;
import com.gitee.zw.irmp.common.auth.vo.LoginUser;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.Collections;
import java.util.Map;

@Slf4j
public class DefaultOAuth2SSOProcessor extends LoginBaseProcessor implements SSOProcessor {

    public static final String OAUTH2_TOKEN = "access_token";

//    private static final String FORM = "form";
//    private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
//
//    private static final String MISSING_USER_INFO_URI_ERROR_CODE = "missing_user_info_uri";
//
//    private static final String MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE = "missing_user_name_attribute";


    @Override
    public void processSSO(HttpServletRequest request, HttpServletResponse response) {
        String token = JWTFilter.getToken(request);
        if (token != null) {
            LoginUser loginUser = userSessionStore.getLoginUser(token);
            if (loginUser != null) {
                log.info("User already login!");
                return;
            }
        }

        token = getTokenFromCode(request);
        request.setAttribute(AUTH_COOKIE_NAME, token);
        JWTFilter.setAuthHeader(token, response);
        Map<String, Object> userInfo = getUserInfo(request, token);
        if (!validUserInfo(userInfo)) {
            log.error("User info is invalid: {}", userInfo);
            throw new AuthenticationException("User info is invalid");
        }
        String userKey = (String) userInfo.get(configuration.getUserNameAttribute());
        log.info("user id: {}", userKey);
        if (userKey == null && !StringUtils.isEmpty(configuration.getUserDetailUri())) {
            Map<String, Object> userDetailAttrs = getUserDetail(request, token, userInfo);
            if (!validUserDetail(userDetailAttrs)) {
                log.error("User detail is invalid: {}", userDetailAttrs);
                throw new AuthenticationException("User detail is invalid");
            }
            log.info("user detail: {}", userDetailAttrs);
            userKey = (String) userDetailAttrs.get(configuration.getUserNameAttribute());
        }
        if (userKey == null) {
            log.error("User name attribute [{}] not found in user info and detail: {}", configuration.getUserNameAttribute(), userInfo);
            throw new AuthenticationException("User name attribute [" + configuration.getUserNameAttribute() + "] not found in user info and detail ");
        }

        LoginUser userByUserSSOKey = userService.getUserByUserSSOKey(userKey);
        if (userByUserSSOKey == null) {
            log.error("User not found: {}", userKey);
            throw new AuthenticationException("User not found");
        }
        userSessionStore.setLoginUser(token, userByUserSSOKey);

    }

    protected boolean validUserInfo(Map<String, Object> userInfo) {
        return true;
    }

    protected boolean validUserDetail(Map<String, Object> userDetail) {
        return true;
    }

    protected Map<String, Object> getUserDetail(HttpServletRequest request, String token, Map<String, Object> userId) {
        RequestEntity<?> detailRequest = getUserDetailRequest(token, userId);
        return getResponse(detailRequest).getBody();
    }

    protected Map<String, Object> getUserInfo(HttpServletRequest userRequest, String token) {
        String userNameAttributeName = configuration.getUserNameAttribute();
        if (StringUtils.isEmpty(userNameAttributeName)) {
            String oauth2Error = "Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "
                    + configuration.getProvider();
            throw new AuthenticationException(oauth2Error);
        }
        RequestEntity<?> request = getUserIdRequest(token, null);
        ResponseEntity<Map<String, Object>> response = getResponse(request);
        Map<String, Object> userAttributes = response.getBody();
        log.info("userAttributes: {}", userAttributes);
        return userAttributes;
    }

    public RequestEntity<?> getUserDetailRequest(String token, Map<String, Object> userId) {

        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        StringBuilder userDetailUri = new StringBuilder(configuration.getUserDetailUri());
        userId.forEach((k, v) -> {
            String replaceHolder = "{" + k + "}";
            int idx = userDetailUri.indexOf(replaceHolder);
            if (idx >= 0) {
                userDetailUri.replace(idx, idx + replaceHolder.length(), v.toString());
            }
        });
        URI uri = UriComponentsBuilder
                .fromUriString(userDetailUri.toString()).build().toUri();

        RequestEntity<?> request;
//        if (HttpMethod.POST.equals()) {
//            headers.setContentType(DEFAULT_CONTENT_TYPE);
//            MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
//            formParameters.add(OAuth2ParameterNames.ACCESS_TOKEN, userRequest.getAccessToken().getTokenValue());
//            request = new RequestEntity<>(formParameters, headers, httpMethod, uri);
//        } else {
        setAuthToken(headers, token);
        request = new RequestEntity<>(headers, HttpMethod.POST, uri);
//        }

        return request;
    }

    public RequestEntity<?> getUserIdRequest(String token, String code) {

        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        URI uri = UriComponentsBuilder
                .fromUriString(configuration.getUserInfoUri()).build().toUri();

        RequestEntity<?> request;
        setAuthToken(headers, token);
        request = new RequestEntity<>(headers, HttpMethod.GET, uri);

        return request;
    }

    public RequestEntity<?> getTokenRequest(String code) {

        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        URI uri = UriComponentsBuilder
                .fromUriString(configuration.getTokenUri().replace("{code}", code)).build().toUri();

        RequestEntity<?> request;
//        if (HttpMethod.POST.equals()) {
//            headers.setContentType(DEFAULT_CONTENT_TYPE);
//            MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
//            formParameters.add(OAuth2ParameterNames.ACCESS_TOKEN, userRequest.getAccessToken().getTokenValue());
//            request = new RequestEntity<>(formParameters, headers, httpMethod, uri);
//        } else {
        request = new RequestEntity<>(headers, HttpMethod.GET, uri);
//        }

        return request;
    }

    protected String getTokenFromCode(HttpServletRequest request) {
        String code = request.getParameter("code");
        RequestEntity<?> req = getTokenRequest(code);
        ResponseEntity<Map<String, Object>> tokenAttributes = getResponse(req);
        log.info("token response: {}", tokenAttributes);
        String token = (String) tokenAttributes.getBody().get(OAUTH2_TOKEN);
        if (StringUtils.isEmpty(token)) {
            String oauth2Error = "Missing required \"access token\" in token response: " + tokenAttributes;
            throw new AuthenticationException(oauth2Error);
        }
        return token;
    }


    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response) {

    }
}
